/**
 * RExt.DataViewEditor
 * A plugin that provides handlers for load, edit, and delete actions on a DataView Record
 * load is used to provide data for editing
 *
 */
RExt.DataViewEditor = function(params){

    this.addEvents({
        'edit': true,
        'delete': true,
        'load': true
    });

    // add supplied listeners
    if (!Ext.isEmpty(params.listeners)) {
       this.on(params.listeners);
    }

    RExt.DataViewEditor.superclass.constructor.apply(this, arguments);
}

Ext.extend(RExt.DataViewEditor, Ext.util.Observable, {

    /**
     * init
     * called automatically by Ext Plugin architecture
     * @param {Ext.DataView} view
     */
    init : function(view) {
        view.on('beforeclick', function (v, index, node, ev){
            if (ev.getTarget('a.media',2,true)) {
                return false;
            }
        },this);
        view.on('click', this.onClick, this);
        view.on('dblclick', this.onDblClick, this);
    },


    /**
     * onClick
     * depending on the click type, fire off the correct event for it, edit, delete etc.
     * @param {Ext.DataView} v
     * @param {Number} index
     * @param {HTMLElement} node
     * @param {Ext.EventObject} ev
     */
    onClick : function(v, index, node, ev) {
        // check two levels deep, in case we are using an image instead if just text inside the <a></a> element
        if (ev.getTarget('a.delete',2,true)) {
            this.fireEvent('delete', v, index, node, ev);
        }
        else if (ev.getTarget('a.edit', 2, true)) {
            this.fireEvent('edit', v, index, node, ev);
        }
    },

    /**
     * onDblClick
     * @param {Ext.DataView} v
     * @param {Number} index
     * @param {HTMLElement} node
     * @param {Ext.EventObject} ev
     */
    onDblClick : function (view, index, node, ev) {
        // send a double click to the onEdit for now
        this.fireEvent('edit', view, index, node, ev);
    }

});

/**
 * RExt.DataView.Orderable
 * A DataView plugin for draggable re-ordering of records
 * @author Chris Scott
 */
RExt.DataView.Orderable = function(params) {
    this.addEvents({
        /**
         * @event edit fires when "edit" link is pressed
         * @param {DataView} view
         * @param {Number} startIndex
         * @param {Records[]} affected records from dragIndex -> dropIndex
         */
        'order' : true
    });

    // add supplied listeners
    if (!Ext.isEmpty(params.listeners)) {
        this.on(params.listeners);
    }
}
Ext.extend(RExt.DataView.Orderable, Ext.util.Observable, {

    /**
     * @cfg {String}
     * Css class to apply to valid dropZone
     * @param {Object} panel
     */
    dropZoneCls: 'r-dataview-dropzone',

    init : function(panel) {
        var vw = panel.getView();
        vw.on('render', function(view) {
            // once rendered, make sure this view offers scrolling as part of the drag drop shit
            Ext.applyIf(view, {
                dropZoneCls: this.dropZoneCls
            });
            this.initDraggables(view);
            this.initDroppables(view);
        }, this);
    },

    initDraggables : function(view) {
        view.dragZone = new Ext.dd.DragZone(view.getEl(), {
            containerScroll: true,

            // On receipt of a mousedown event, see if it is within a draggable element.
            // Return a drag data object if so. The data object can contain arbitrary application
            // data, but it should also contain a DOM element in the ddel property to provide
            // a proxy to drag.
            getDragData: function(e) {
                var sourceEl = e.getTarget(view.itemSelector);
                if (sourceEl) {
                    d = sourceEl.cloneNode(true);
                    d.id = Ext.id();
                    return view.dragData =  {
                        sourceEl: sourceEl,
                        repairXY: Ext.fly(sourceEl).getXY(),
                        ddel: d,
                        record: view.getRecord(sourceEl) // <-- hack 1 for index
                    }
                }
            },

            // Provide coordinates for the proxy to slide back to on failed drag.
            // This is the original XY coordinates of the draggable element.
            getRepairXY: function() {
                return this.dragData.repairXY;
            }
        });
    },

    /*
     * Here is where we "activate" the GridPanel.
     * We have decided that the element with class "hospital-target" is the element which can receieve
     * drop gestures. So we inject a method "getTargetFromEvent" into the DropZone. This is constantly called
     * while the mouse is moving over the DropZone, and it returns the target DOM element if it detects that
     * the mouse if over an element which can receieve drop gestures.
     *
     * Once the DropZone has been informed by getTargetFromEvent that it is over a target, it will then
     * call several "onNodeXXXX" methods at various points. These include:
     *
     * onNodeEnter
     * onNodeOut
     * onNodeOver
     * onNodeDrop
     *
     * We provide implementations of each of these to provide behaviour for these events.
     */
    initDroppables : function(view) {
        var self = this;
        view.dropZone = new Ext.dd.DropZone(view.getEl(), {

            defaultPadding: {
                left: 0,
                right: 0,
                top: 5,
                bottom: 5
            },

            // If the mouse is over a target node, return that node. This is
            // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
            getTargetFromEvent: function(e) {
                return e.getTarget(view.itemSelector);
            },

            // On entry into a target node, highlight that node.
            onNodeEnter : function(target, dd, e, data){
                Ext.fly(target).addClass(view.dropZoneCls);
            },

            // On exit from a target node, unhighlight that node.
            onNodeOut : function(target, dd, e, data){
                Ext.fly(target).removeClass(view.dropZoneCls);
            },

            // While over a target node, return the default drop allowed class which
            // places a "tick" icon into the drag proxy.
            // thing is, self is not a good target node for dropping...
            onNodeOver : function(target, dd, e, data){
                return (target.id != view.dragData.sourceEl.id)? Ext.dd.DropZone.prototype.dropAllowed : Ext.dd.DropZone.prototype.dropNotAllowed;
            },

            // On node drop, we can interrogate the target node to find the underlying
            // application object that is the real target of the dragged data.
            // In this case, it is a Record in the GridPanel's Store.
            // We can use the data set up by the DragZone's getDragData method to read
            // any data we decided to attach.
            onNodeDrop : function(target, dd, e, data) {

                if ( view.getRecord(target).id == data.record.id) {
                    return false;        // don't drop a record on yourself!!
                }

                // calculate the dragIndex, and remove the record, noting we may have to re-insert
                // the record back into the store at the same place if we dropped on this original node
                var dragIndex = view.store.indexOf(data.record);
                view.store.remove(data.record);

                var dropRecord = view.getRecord(target);
                var dropIndex = view.store.indexOf(dropRecord);

                if (dropIndex < 0) {
                    dropIndex = 0;
                }

                if (dragIndex <= dropIndex) {
                    dropIndex++;
                }
                var dragRecord = view.store.insert(dropIndex, data.record);
                view.select(dropIndex);

                var index = dragIndex;
                var rs = null;
                if (dragIndex > dropIndex) {
                    index = dropIndex;
                    rs = view.store.getRange(dropIndex, dragIndex);
                }
                else {
                    rs = view.store.getRange(dragIndex, dropIndex);
                }

                self.fireEvent('order', view, index, rs);

            }
        });
    }
});

/**
 * RExt.DataView.Sort
 * A DataView plugin for draggable re-ordering of records
 */
RExt.DataView.Sort = function(params) {
    this.addEvents({
        /**
         * @event edit fires when "edit" link is pressed
         * @param {DataView} view
         * @param {Number} startIndex
         * @param {Records[]} affected records from dragIndex -> dropIndex
         */
        'order' : true
    });

    // add supplied listeners
    if (!Ext.isEmpty(params.listeners)) {
        this.on(params.listeners);
    }
}
Ext.extend(RExt.DataView.Sort, Ext.util.Observable, {

    /**
     * @cfg {String}
     * Css class to apply to valid dropZone
     * @param {Object} panel
     */
    dropZoneCls: 'r-dataview-dropzone',

    init : function(panel) {
        var vw = panel.getView();
        vw.on('render', function(view) {
            // once rendered, make sure this view offers scrolling as part of the drag drop shit
            Ext.applyIf(view, {
                dropZoneCls: this.dropZoneCls
            });
            this.initDraggables(view);
            this.initDroppables(view);
        }, this);
    },

    initDraggables : function(view) {
        view.dragZone = new Ext.dd.DragZone(view.getEl(), {
            containerScroll: true,

            // On receipt of a mousedown event, see if it is within a draggable element.
            // Return a drag data object if so. The data object can contain arbitrary application
            // data, but it should also contain a DOM element in the ddel property to provide
            // a proxy to drag.
            getDragData: function(e) {
                var sourceEl = e.getTarget(view.itemSelector);
                if (sourceEl) {
                    d = sourceEl.cloneNode(true);
                    d.id = Ext.id();
                    return view.dragData =  {
                        sourceEl: sourceEl,
                        repairXY: Ext.fly(sourceEl).getXY(),
                        ddel: d,
                        record: view.getRecord(sourceEl) // <-- hack 1 for index
                    }
                }
            },

            // Provide coordinates for the proxy to slide back to on failed drag.
            // This is the original XY coordinates of the draggable element.
            getRepairXY: function() {
                return this.dragData.repairXY;
            }
        });
    },

    /*
     * Here is where we "activate" the GridPanel.
     * We have decided that the element with class "hospital-target" is the element which can receieve
     * drop gestures. So we inject a method "getTargetFromEvent" into the DropZone. This is constantly called
     * while the mouse is moving over the DropZone, and it returns the target DOM element if it detects that
     * the mouse if over an element which can receieve drop gestures.
     *
     * Once the DropZone has been informed by getTargetFromEvent that it is over a target, it will then
     * call several "onNodeXXXX" methods at various points. These include:
     *
     * onNodeEnter
     * onNodeOut
     * onNodeOver
     * onNodeDrop
     *
     * We provide implementations of each of these to provide behaviour for these events.
     */
    initDroppables : function(view) {
        var self = this;
        view.dropZone = new Ext.dd.DropZone(view.getEl(), {

            defaultPadding: {
                left: 0,
                right: 0,
                top: 5,
                bottom: 5
            },

            // If the mouse is over a target node, return that node. This is
            // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
            getTargetFromEvent: function(e) {
                return e.getTarget(view.itemSelector);
            },

            // On entry into a target node, highlight that node.
            onNodeEnter : function(target, dd, e, data){
                Ext.fly(target).addClass(view.dropZoneCls);
            },

            // On exit from a target node, unhighlight that node.
            onNodeOut : function(target, dd, e, data){
                Ext.fly(target).removeClass(view.dropZoneCls);
            },

            // While over a target node, return the default drop allowed class which
            // places a "tick" icon into the drag proxy.
            // thing is, self is not a good target node for dropping...
            onNodeOver : function(target, dd, e, data){
                return (target.id != view.dragData.sourceEl.id)? Ext.dd.DropZone.prototype.dropAllowed : Ext.dd.DropZone.prototype.dropNotAllowed;
            },

            // On node drop, we can interrogate the target node to find the underlying
            // application object that is the real target of the dragged data.
            // In this case, it is a Record in the GridPanel's Store.
            // We can use the data set up by the DragZone's getDragData method to read
            // any data we decided to attach.
            onNodeDrop : function(target, dd, e, data) {

                if ( view.getRecord(target).id == data.record.id) {
                    return false;        // don't drop a record on yourself!!
                }

                // calculate the dragIndex, and remove the record, noting we may have to re-insert
                // the record back into the store at the same place if we dropped on this original node
                var dragIndex = view.store.indexOf(data.record);
                view.store.remove(data.record);

                var dropRecord = view.getRecord(target);
                var dropIndex = view.store.indexOf(dropRecord);

                if (dropIndex < 0) {
                    dropIndex = 0;
                }

                if (dragIndex <= dropIndex) {
                    dropIndex++;
                }
                var dragRecord = view.store.insert(dropIndex, data.record);
                view.select(dropIndex);

                var index = dragIndex;
                var rs = null;
                if (dragIndex > dropIndex) {
                    index = dropIndex;
                    rs = view.store.getRange(dropIndex, dragIndex);
                }
                else {
                    rs = view.store.getRange(dragIndex, dropIndex);
                }
                console.info('onNodeDrop firing an order event');
                self.fireEvent('order', view, index, rs);

            }
        });
    }
});

/**
 * RExt.DataView.HoverEditable
 * A Shopify / 37Signals-like hover-edit-tab.
 * @author Chris Scott
 */
RExt.DataView.HoverEditable = function(params){

    this.addEvents({
        /**
         * @event edit fires when "edit" link is pressed
         * @param {Ext.DataView} view
         * @param {Number} index
         * @param {EventObject} ev
         */
        'edit' : true
    });

    // add supplied listeners
    if (!Ext.isEmpty(params.listeners)) {
        this.on(params.listeners);
    }

}
Ext.extend(RExt.DataView.HoverEditable, Ext.util.Observable, {

    init : function(panel) {
        var popup = Ext.getBody().createChild({
            tag: 'span',
            cls: 'r-dataview-hover-editable',
            html: '<a class="tool edit" href="#">Edit</a>',
            style: {
                border: '1px solid red'
            }
        });

        var task = new Ext.util.DelayedTask(function() {
            popup.hide();
        });

        var view = panel.getView();
        view.on('mouseenter', function(v, index, node, ev) {
            popup.show();
            popup.index = index;
            popup.alignTo(node, 'tr-tl?');
            task.cancel();
        });
        view.on('mouseleave', function(v, index, node, ev) {
            task.delay(1000);
        });
        popup.on('mouseover', function() {
            task.cancel();
        });
        popup.on('mouseout', function() {
            task.delay(1000);
        });
        popup.on('click', function(ev, node, options) {
            var el = ev.getTarget('a.tool', 2, true);
            if (el.hasClass('tool')) {
                view.select(popup.index);
                if (el.hasClass('edit')) {
                    ev.stopEvent();
                    this.fireEvent('edit', view, popup.index, ev);
                }
            }
        },this);
    }
});

/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */
/**
 * Ext.DataView.LabelEditor
 * @param {Object} cfg
 * @param {Object} field
 */
Ext.DataView.LabelEditor = function(cfg, field){
    Ext.DataView.LabelEditor.superclass.constructor.call(this,
        field || new Ext.form.TextField({
            allowBlank: false,
            growMin:90,
            growMax:240,
            grow:true,
            selectOnFocus:true
        }), cfg
    );
}

Ext.extend(Ext.DataView.LabelEditor, Ext.Editor, {
    alignment: "tl-tl",
    hideEl : false,
    cls: "x-small-editor",
    shim: false,
    completeOnEnter: true,
    cancelOnEsc: true,
    labelSelector: 'span.x-editable',

    init : function(view){
        this.view = view;
        view.on('render', this.initEditor, this);
        this.on('complete', this.onSave, this);
    },

    initEditor : function(){
        this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});
    },

    onMouseDown : function(e, target){
        if(!e.ctrlKey && !e.shiftKey){
            var item = this.view.findItemFromChild(target);
            e.stopEvent();
            var record = this.view.store.getAt(this.view.indexOf(item));
            this.startEdit(target, record.data[this.dataIndex]);
            this.activeRecord = record;
        }else{
            e.preventDefault();
        }
    },

    onSave : function(ed, value){
        this.activeRecord.set(this.dataIndex, value);
    }
});

/**
 * Ext.DataView.DragSelector
 * @param {Object} cfg
 */
Ext.DataView.DragSelector = function(cfg){
    cfg = cfg || {};
    var view, regions, proxy, tracker;
    var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);
    var dragSafe = cfg.dragSafe === true;

    this.init = function(dataView){
        view = dataView;
        view.on('render', onRender);
    };

    function fillRegions(){
        rs = [];
        view.all.each(function(el){
            rs[rs.length] = el.getRegion();
        });
        bodyRegion = view.el.getRegion();
    }

    function cancelClick(){
        return false;
    }

    function onBeforeStart(e){
        return !dragSafe || e.target == view.el.dom;
    }

    function onStart(e){
        view.on('containerclick', cancelClick, view, {single:true});
        if(!proxy){
            proxy = view.el.createChild({cls:'x-view-selector'});
        }else{
            proxy.setDisplayed('block');
        }
        fillRegions();
        view.clearSelections();
    }

    function onDrag(e){
        var startXY = tracker.startXY;
        var xy = tracker.getXY();

        var x = Math.min(startXY[0], xy[0]);
        var y = Math.min(startXY[1], xy[1]);
        var w = Math.abs(startXY[0] - xy[0]);
        var h = Math.abs(startXY[1] - xy[1]);

        dragRegion.left = x;
        dragRegion.top = y;
        dragRegion.right = x+w;
        dragRegion.bottom = y+h;

        dragRegion.constrainTo(bodyRegion);
        proxy.setRegion(dragRegion);

        for(var i = 0, len = rs.length; i < len; i++){
            var r = rs[i], sel = dragRegion.intersect(r);
            if(sel && !r.selected){
                r.selected = true;
                view.select(i, true);
            }else if(!sel && r.selected){
                r.selected = false;
                view.deselect(i);
            }
        }
    }

    function onEnd(e){
        if(proxy){
            proxy.setDisplayed(false);
        }
    }

    function onRender(view){
        tracker = new Ext.dd.DragTracker({
            onBeforeStart: onBeforeStart,
            onStart: onStart,
            onDrag: onDrag,
            onEnd: onEnd
        });
        tracker.initEl(view.el);
    }
};
